<?php
/**
 * Created by PhpStorm.
 * User: jiangjunxian
 * Date: 2016/11/28
 * Time: 11:07
 */

namespace app\modules\admin\models;

use app\modules\admin\components\Configs;
use app\modules\admin\components\RouteRule;
use Yii;
use yii\base\Controller;
use yii\base\Exception;
use yii\base\Module;
use yii\base\Object;
use yii\caching\TagDependency;
use yii\helpers\VarDumper;
use yii\web\User;

class Route extends Object
{
    const CACHE_TAG = 'jjx.admin.route';
	/**
	 * @var array 用户路由列表
	 */
	private static $_userRoutes;
	private static $_defaultRoutes;

	/**
	 * 说明: 获取存在的跌幅
	 * @param bool $withKey 是否带键名
	 * @param bool $withWildcards 是否包含通配符路由
	 * @return array|mixed
	 */
	public static function getAssignedRoutes($withKey = false, $withWildcards = true) {
        $key = [__METHOD__ . strval($withKey), Yii::$app->uniqueId];
        $cache = Configs::cache();
        if(($routes = $cache->get($key)) === false) {
            if($cache != null) {
                $routes = [];
                $permissions = Yii::$app->authManager->getPermissions();
                foreach ($permissions as $name=>$value) {
                    if($name[0] === '/' && ($withWildcards && substr($name, -1) != '*')) {
                        if($withKey) $routes[$name] = $name; else $routes[] = $name;
                    }
                }
                $cache->set($key, $routes, Configs::instance()->cacheDuration, new TagDependency(['tags' => self::CACHE_TAG]));
            }
        }
        return $routes;
    }

	/**
     * 获取或者新建路由到权限系统
     * @param [] $routes 获取的路由列表
     */
    public function assign($routes) {
        $mgr = Yii::$app->authManager;
        foreach ($routes as $route) {
            try {
                $r = explode('&', $route); // 参数
                $item = $mgr->createPermission('/' . trim($route, '/'));
                if (count($r) > 1) { // 带参数的，要对参数生成相应的rule
                    $action = '/' . trim($r[0], '/');
                    if (($itemAction = $mgr->getPermission($action)) === null) { // 如果路由不存在的话，新建路由
                        $itemAction = $mgr->createPermission($action);
                        $mgr->add($itemAction);
                    }
                    unset($r[0]); // 删除掉路由
                    foreach ($r as $part) {
                        $part = explode('=', $part);
                        $item->data['params'][$part[0]] = isset($part[1]) ? $part[1] : '';
                    }

                    $this->setDefaultRule(); // 建立默认的Rule
                    $item->ruleName = RouteRule::RULE_NAME;
                    $mgr->add($item);
                    $mgr->addChild($item, $itemAction);
                } else {
                    $mgr->add($item);
                }
            } catch (Exception $exception) {
                Yii::error($exception->getMessage(), __METHOD__);
            }
        }
        $this->invalidateRouteCache(); // 清理缓存
    }

    /**
     * 从权限系统中删除路由
     * @param [] $routes 路由列表
     */
    public function remove($routes) {
        $mgr = Yii::$app->authManager;
        foreach ($routes as $route) {
            try {
                $item = $mgr->createPermission('/' . trim($route, '/'));
                $mgr->remove($item);
            } catch (Exception $exception) {
                Yii::error($exception->getMessage(), __METHOD__);
            }
        }
        $this->invalidateRouteCache(); // 清理缓存
    }

    /**
     * 清除路由缓存
     * @return $this
     */
    public function refresh() {
        $this->invalidateRouteCache();
        return $this;
    }

    /**
     * 获取应用和获取的路由列表
     * @return array
     */
    public function getRoutes() {
        $availablePermission = array_keys(Yii::$app->authManager->getPermissions());
        $routes = $this->getAppRoutes();
        $exists = [];
        foreach ($availablePermission as $item) {
            if($item[0] !== '/') {
                continue;
            }
            $exists[] = $item;
            unset($routes[$item]);
        }
        return [
            'available' => array_keys($routes),
            'assigned' => $exists,
        ];
    }

    /**
     * 获取应用的路由列表
     * @param null $module
     * @return array|mixed
     */
    private function getAppRoutes($module = null)
    {
        if($module === null) {
            $module = Yii::$app;
        } elseif (is_string($module)) {
            $module = Yii::$app->getModule($module);
        }

        $key = [__METHOD__, $module->getUniqueId()];
        $cache = Configs::instance()->cache;

        if (($result = $cache->get($key)) === false) {
            if ($cache !== null) { // 把路由信息缓存起来。
                $result = [];
                $this->getRouteRecursive($module, $result);
                $cache->set($key, $result, Configs::instance()->cacheDuration, new TagDependency(['tags'=>self::CACHE_TAG]));
            }
        }

        return $result;
    }

    /**
     * 开始递归路由
     * @param Module $module
     * @param $result
     */
    private function getRouteRecursive($module, &$result)
    {
        $token = 'Get Route of \'' . get_class($module) . '\' with id \'' . $module->uniqueId . '\'';
        Yii::beginProfile($token, __METHOD__);
        try {
            foreach ($module->getModules() as $id => $child) { // 获取子模块
                if(($child = $module->getModule($id)) !== null) {
                    $this->getRouteRecursive($child, $result);
                }
            }

            foreach ($module->controllerMap as $id => $type) {
                $this->getControllerActions($type, $id, $module, $result);
            }

            $namespace = trim($module->controllerNamespace, '\\') . '\\';
            $this->getControllerFiles($module, $namespace, '', $result);
            $all = '/' . ltrim($module->uniqueId . '/*', '/');
            $result[$all] = $all;
        } catch (\Exception $exception) {
            Yii::error($exception->getMessage(), __METHOD__);
        }
        Yii::endProfile($token, __METHOD__);
    }

    /**
     * Get controller list under module
	 * 说明: 获取模块下面的控制器列表
     * @param Module $module
     * @param string $namespace
     * @param string $prefix
     * @param mixed $result
     */
    private function getControllerFiles($module, $namespace, $prefix, &$result)
    {
        $path = Yii::getAlias('@' . str_replace('\\', '/' , $namespace), false);
        $token = "Get controllers form '$path'";
        Yii::beginProfile($token, __METHOD__);
        try {
            if(!is_dir($path)) {
                return;
            }

            foreach (scandir($path) as $file) {
                if($file == '.' || $file == '..') {
                    continue;
                }

                if(is_dir($path . DIRECTORY_SEPARATOR . $file)
                    && preg_match('%^[a-z0-9_/]+$%i', $file . DIRECTORY_SEPARATOR)) {
                    $this->getControllerFiles($module, $namespace.$file.'\\', $prefix . $file . DIRECTORY_SEPARATOR, $result);
                } elseif (strcmp(substr($file, -14), 'Controller.php') === 0) {
                    $baseName = substr(basename($file), 0, -14);
                    $name = strtolower(preg_replace('/(?<![A-Z])[A-Z]/', '\0', $baseName));
                    $id = ltrim(str_replace(' ', '-', $name), '-');
                    $className = $namespace . $baseName . 'Controller';
                    if(strpos($className, '-') === false && class_exists($className) && is_subclass_of($className, 'yii\base\Controller')) {
                        $this->getControllerActions($className, $prefix . $id, $module, $result);
                    }
                }
            }
        } catch (\Exception $exception) {
            Yii::error($exception->getMessage(), __METHOD__);
        }
        Yii::endProfile($token, __METHOD__);
    }

	/**
	 * Get list action of controller
	 * 说明: 获取控制器(controller)中的方法(action)
	 * @param mixed $type
	 * @param string $id
	 * @param Module $module
	 * @param mixed $result
	 */
	private function getControllerActions($type, $id, $module, &$result)
	{
		$token = ' Create controller with config=' . VarDumper::dumpAsString($type) . " and id='$id'";
		Yii::beginProfile($token, __METHOD__);
		try {
			/** @var Controller $controller */
			$controller = Yii::createObject($type, [$id, $module]);
			$this->getActionRoutes($controller, $result);
			$all = "/{$controller->uniqueId}/*";
			$result[$all] = $all;
		} catch (\Exception $exception) {
			Yii::error($exception->getMessage(), __METHOD__);
		}
		Yii::endProfile($token, __METHOD__);
	}

	/**
	 * Get route of action
	 * 说明: 获取action的路由
	 * @param Controller $controller
	 * @param array $result all controller actions
	 */
	private function getActionRoutes($controller, &$result)
	{
		$token = 'Get actions of controller \'' . $controller->uniqueId . '\'';
		Yii::beginProfile($token, __METHOD__);
		try {
			$prefix = '/'. $controller->uniqueId . '/';
			foreach ($controller->actions() as $id => $value) {
				$result[$prefix . $id] = $prefix . $id;
			}
			$class = new \ReflectionClass($controller);
			foreach ($class->getMethods() as $method) {
				$name = $method->name;
				if($method->isPublic() && !$method->isStatic() && strpos($name, 'action')===0 && $name !== 'actions') {
					$name = strtolower(preg_replace('/(?<![A-Z])[A-Z]/', '\0', substr($name, 6)));
					$id = $prefix . ltrim(str_replace(' ', '-', $name), '-');
					$result[$id] = $id;
				}
			}
		} catch (\Exception $exception) {
			Yii::error($exception->getMessage(), __METHOD__);
		}
		Yii::endProfile($token, __METHOD__);
	}

	/**
	 * 说明: 设置默认的规则
	 */
	private function setDefaultRule()
    {
        if (Yii::$app->authManager->getRule(RouteRule::RULE_NAME)) {
            Yii::$app->authManager->add(new RouteRule());
        }
    }

	/**
	 * 说明: 手动过期路由缓存
	 */
	private function invalidateRouteCache() {
        if(Configs::cache() !== null) {
            TagDependency::invalidate(Configs::cache(), self::CACHE_TAG);
        }
    }

	/**
	 * 说明: 查检用户的路由权限
	 * @param $route
	 * @param array $params
	 * @param integer|User $user
	 * @return bool
	 */
	public static function checkRoute($route, $params = [], $user = null) {
		$config = Configs::instance();
		$r = self::normalizeRoute($route);
		if($config->onlyRegisteredRoute && !isset(self::getAssignedRoutes(true, true)[$r])) { // 只要注册过的验证,都可以通过验证
			return true;
		}

		if($user === null) {
			$user = Yii::$app->getUser();
		}
		$userId = $user instanceof User ? $user->getId() : $user;

		if($config->strict) {
			if($user->can($r, $params)) { // 找到路由进行验证
				return true;
			}
			while (($pos = strrpos($r, '/')) > 0) { // 递归路由
				$r = substr($r, 0, $pos);
				if($user->can($r . '/*', $params)) { // 验证通配符路由
					return true;
				}
			}
			return $user->can('/*', $params); // 是否拥有全居权限,超级管理员
		} else { // 将不检查Rule规则
			$routes = self::getRoutesByUser($userId); // 获取用户的路由
			if(isset($routes[$r])) {
				return true;
			}
			while (($pos = strrpos($r, '/')) > 0) { // 递归路由
				$r = substr($r, 0, $pos);
				if (isset($routes[$r . '/*'])) {
					return true;
				}
			}
			return isset($routes['/*']); // 是否拥有全局权限,超级管理员
		}
	}

	/**
	 * 说明: 获取用户的所有路由
	 * @param integer $userId
	 * @return mixed
	 */
	private static function getRoutesByUser($userId) {
		if(!isset(self::$_userRoutes[$userId])) {
			$cache = Configs::cache();
			if($cache && ($routes = $cache->get([__METHOD__, $userId])) !== false) {
				self::$_userRoutes[$userId] = $routes; // 从缓存中获取
			} else {
				$routes = self::getDefaultRoutes();
				$userPermissions = Yii::$app->authManager->getPermissionsByUser($userId);
				foreach ($userPermissions as $item) {
					if($item->name[0] === '/') {
						$routes[$item->name] = true;
					}
				}
				self::$_userRoutes[$userId] = $routes;
				if($cache) {
					$cache->set([__METHOD__, $userId], $routes, Configs::cacheDuration(), new TagDependency([
						'tags' => self::CACHE_TAG
					]));
				}
			}
		}
		return self::$_userRoutes[$userId];
	}

	/**
	 * 说明: 获取默认的路由
	 * @return array
	 */
	private static function getDefaultRoutes()
	{
		if(self::$_defaultRoutes === null) {
			/** @var $mgr \yii\rbac\BaseManager */
			$mgr = Yii::$app->authManager;
			$roles = $mgr->defaultRoles;
			$cache = Configs::cache();
			if ($cache && ($routes = $cache->get($roles)) !== false) {
				self::$_defaultRoutes = $routes;
			} else {
				$permissions = self::$_defaultRoutes = [];
				foreach ($roles as $role) {
					$permissions = array_merge($permissions, $mgr->getPermissionsByRole($role));
				}
				foreach ($permissions as $permission) {
					if ($permission->name[0] === '/') {
						self::$_defaultRoutes[$permission->name] = true;
					}
				}
				if($cache) {
					$cache->set($roles, self::$_defaultRoutes, Configs::cacheDuration(), new TagDependency([
						'tags' => self::CACHE_TAG
					]));
				}
			}
		}
		return self::$_defaultRoutes;
	}

	/**
	 * 说明: 规范化路由名称
	 * @param string $route
	 * @return string
	 */
	private static function normalizeRoute($route) {
		if ($route === '') { // route is empty
			return '/' . Yii::$app->controller->getRoute();
		} elseif (strncmp($route, '/', 1) === 0) { // route is full route
			return $route;
		} elseif (strpos($route, '/') === false) { // route is action name
			return '/' . Yii::$app->controller->getUniqueId() . '/' . $route;
		} elseif (($mid = Yii::$app->controller->module->getUniqueId()) !== '') { // route form model
			return '/' . $mid . '/' . $route;
		}
		return '/' . $route;
	}
}